home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue66 / Alfresco / AARegex.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2001-01-02  |  37.0 KB  |  1,204 lines

  1. {*********************************************************}
  2. {* AARegex                                               *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Regular expression classes       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AARegex;
  14.  
  15. interface
  16.  
  17. {Notes: these classes parse regular expressions that follow this
  18.         grammar:
  19.  
  20.         <anchorexpr> ::= <expr> |
  21.                          '^' <expr> |
  22.                          <expr> '$' |
  23.                          '^' <expr> '$'
  24.         <expr> ::= <term> |
  25.                    <term> '|' <expr>                 - alternation
  26.         <term> ::= <factor> |
  27.                    <factor><term>                    - concatenation
  28.         <factor> ::= <atom> |
  29.                      <atom> '?' |                    - zero or one
  30.                      <atom> '*' |                    - zero or more
  31.                      <atom> '+'                      - one or more
  32.         <atom> ::= <char> |
  33.                    '.' |                             - any char
  34.                    '(' <expr> ') |                   - parentheses
  35.                    '[' <charclass> ']' |             - normal class
  36.                    '[^' <charclass> ']'              - negated class
  37.         <charclass> ::= <charrange> |
  38.                         <charrange><charclass>
  39.         <charrange> ::= <ccchar> |
  40.                         <ccchar> '-' <ccchar>
  41.         <char> ::= <any character except metacharacters> |
  42.                    '\' <any character at all>
  43.         <ccchar> ::= <any character except '-' and ']'> |
  44.                      '\' <any character at all>
  45.  
  46.         This means that parentheses have maximum precedence, followed
  47.         by square brackets, followed by the closure operators,
  48.         followed by concatenation, finally followed by alternation.
  49. }
  50.  
  51. {turn this compiler define on to log the parsing progress and the
  52.  final transition table; file is c:\regexparse.log}
  53. {.$DEFINE LogParse}
  54.  
  55. uses
  56.   SysUtils,
  57.   Classes,
  58.   AAIntDeq;
  59.  
  60. type
  61.   TaaRegexParser = class
  62.     private
  63.       FRegexStr : string;
  64.       FPosn     : PAnsiChar;
  65.     protected
  66.       procedure rpParseAtom;
  67.       procedure rpParseCCChar;
  68.       procedure rpParseChar;
  69.       procedure rpParseCharClass;
  70.       procedure rpParseCharRange;
  71.       procedure rpParseExpr;
  72.       procedure rpParseFactor;
  73.       procedure rpParseTerm;
  74.     public
  75.       constructor Create(const aRegexStr : string);
  76.       destructor Destroy; override;
  77.  
  78.       function Parse(var aErrorPos : integer) : boolean;
  79.   end;
  80.  
  81. type
  82.   PaaCharSet = ^TaaCharSet;
  83.   TaaCharSet = set of char;
  84.  
  85.   TaaNFAMatchType = (  {types of matching performed...}
  86.      mtNone,           {..no match (an epsilon no-cost move)}
  87.      mtAnyChar,        {..any character}
  88.      mtChar,           {..a particular character}
  89.      mtClass,          {..a character class}
  90.      mtNegClass,       {..a negated character class}
  91.      mtTerminal,       {..the final state--no matching}
  92.      mtUnused);        {..an unused state--no matching}
  93.  
  94.   TaaRegexError = (    {error codes for invalid regex strings}
  95.      recNone,          {..no error}
  96.      recSuddenEnd,     {..unexpected end of string}
  97.      recMetaChar,      {..read metacharacter, but needed normal char}
  98.      recNoCloseParen,  {..expected close paren, but not there}
  99.      recExtraChars     {..not at end of string after parsing regex}
  100.      );
  101.  
  102.   TaaUpcaseChar = function (aCh : char) : char;
  103.  
  104.   TaaRegexCompiler = class
  105.     private
  106.       FAnchorEnd  : boolean;
  107.       FAnchorStart: boolean;
  108.       FErrorCode  : TaaRegexError;
  109.       FIgnoreCase : boolean;
  110.       FPosn       : PAnsiChar;
  111.       FRegexStr   : string;
  112.       FStartState : integer;
  113.       FTable      : TList;
  114.       FUpcase     : TaaUpcaseChar;
  115.       {$IFDEF LogParse}
  116.       Log : System.Text;
  117.       {$ENDIF}
  118.     protected
  119.       procedure rcSetIgnoreCase(aValue : boolean);
  120.       procedure rcSetRegexStr(const aRegexStr : string);
  121.       procedure rcSetUpcase(aValue : TaaUpcaseChar);
  122.  
  123.       procedure rcClear;
  124.       procedure rcLevel1Optimize;
  125.       function rcMatchSubString(const S   : string;
  126.                                 StartPosn : integer) : boolean;
  127.       function rcAddState(aMatchType : TaaNFAMatchType;
  128.                           aChar      : char;
  129.                           aCharClass : PaaCharSet;
  130.                           aNextState1: integer;
  131.                           aNextState2: integer) : integer;
  132.       function rcSetState(aState     : integer;
  133.                           aNextState1: integer;
  134.                           aNextState2: integer) : integer;
  135.  
  136.       function rcParseAnchorExpr : integer;
  137.       function rcParseAtom : integer;
  138.       function rcParseCCChar : char;
  139.       function rcParseChar : integer;
  140.       function rcParseCharClass(aClass : PaaCharSet) : boolean;
  141.       function rcParseCharRange(aClass : PaaCharSet) : boolean;
  142.       function rcParseExpr : integer;
  143.       function rcParseFactor : integer;
  144.       function rcParseTerm : integer;
  145.  
  146.       {$IFDEF LogParse}
  147.       procedure rcDumpTable;
  148.       {$ENDIF}
  149.     public
  150.       constructor Create(const aRegexStr : string);
  151.       destructor Destroy; override;
  152.  
  153.       function Parse(var aErrorPos : integer;
  154.                      var aErrorCode: TaaRegexError) : boolean;
  155.       function MatchString(const S : string) : integer;
  156.  
  157.  
  158.       property IgnoreCase : boolean
  159.                   read FIgnoreCase write rcSetIgnoreCase;
  160.       property RegexString : string
  161.                   read FRegexStr write rcSetRegexStr;
  162.       property Upcase : TaaUpcaseChar
  163.                   read FUpcase write rcSetUpcase;
  164.   end;
  165.  
  166. implementation
  167.  
  168. const
  169.   MetaCharacters : set of char =
  170.                    ['[', ']', '(', ')', '|', '*', '+', '?', '-', '.',
  171.                     '^', '$'];
  172.   {some handy constants}
  173.   UnusedState = -1;
  174.   NewFinalState = -2;
  175.   CreateNewState = -3;
  176.   ErrorState = -4;
  177.   MustScan = -5;
  178.  
  179. type
  180.   PaaNFAState = ^TaaNFAState;
  181.   TaaNFAState = record
  182.     sdNextState1: integer;
  183.     sdNextState2: integer;
  184.     sdClass     : PaaCharSet;
  185.     sdMatchType : TaaNFAMatchType;
  186.     sdChar      : char;
  187.   end;
  188.  
  189.  
  190. {===TaaRegexParser===================================================}
  191. constructor TaaRegexParser.Create(const aRegexStr : string);
  192. begin
  193.   inherited Create;
  194.   FRegexStr := aRegexStr;
  195. end;
  196. {--------}
  197. destructor TaaRegexParser.Destroy;
  198. begin
  199.   inherited Destroy;
  200. end;
  201. {--------}
  202. function TaaRegexParser.Parse(var aErrorPos : integer) : boolean;
  203. begin
  204.   Result := true;
  205.   aErrorPos := 0;
  206.   FPosn := PAnsiChar(FRegexStr);
  207.   try
  208.     rpParseExpr;
  209.     if (FPosn^ <> #0) then begin
  210.       Result := false;
  211.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  212.     end;
  213.   except
  214.     on E:Exception do begin
  215.       Result := false;
  216.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  217.     end;
  218.   end;
  219. end;
  220. {--------}
  221. procedure TaaRegexParser.rpParseAtom;
  222. begin
  223.   case FPosn^ of
  224.     '(' : begin
  225.             inc(FPosn);
  226.             writeln('open paren');
  227.             rpParseExpr;
  228.             if (FPosn^ <> ')') then
  229.               raise Exception.Create('Regex error: expecting a closing parenthesis');
  230.             inc(FPosn);
  231.             writeln('close paren');
  232.           end;
  233.     '[' : begin
  234.             inc(FPosn);
  235.             if (FPosn^ = '^') then begin
  236.               inc(FPosn);
  237.               writeln('negated char class');
  238.               rpParseCharClass;
  239.             end
  240.             else begin
  241.               writeln('normal char class');
  242.               rpParseCharClass;
  243.             end;
  244.             inc(FPosn);
  245.           end;
  246.     '.' : begin
  247.             inc(FPosn);
  248.             writeln('any character');
  249.           end;
  250.   else
  251.     rpParseChar;
  252.   end;{case}
  253. end;
  254. {--------}
  255. procedure TaaRegexParser.rpParseCCChar;
  256. begin
  257.   if (FPosn^ = #0) then
  258.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  259.   if FPosn^ in [']', '-'] then
  260.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  261.   if (FPosn^ = '\') then begin
  262.     inc(FPosn);
  263.     writeln('escaped ccchar ', FPosn^);
  264.     inc(FPosn);
  265.   end
  266.   else begin
  267.     writeln('ccchar ', FPosn^);
  268.     inc(FPosn);
  269.   end;
  270. end;
  271. {--------}
  272. procedure TaaRegexParser.rpParseChar;
  273. begin
  274.   if (FPosn^ = #0) then
  275.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  276.   if FPosn^ in MetaCharacters then
  277.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  278.   if (FPosn^ = '\') then begin
  279.     inc(FPosn);
  280.     writeln('escaped char ', FPosn^);
  281.     inc(FPosn);
  282.   end
  283.   else begin
  284.     writeln('char ', FPosn^);
  285.     inc(FPosn);
  286.   end;
  287. end;
  288. {--------}
  289. procedure TaaRegexParser.rpParseCharClass;
  290. begin
  291.   rpParseCharRange;
  292.   if (FPosn^ <> ']') then
  293.     rpParseCharClass;
  294. end;
  295. {--------}
  296. procedure TaaRegexParser.rpParseCharRange;
  297. begin
  298.   rpParseCCChar;
  299.   if (FPosn^ = '-') then begin
  300.     inc(FPosn);
  301.     writeln('--range to--');
  302.     rpParseCCChar;
  303.   end;
  304. end;
  305. {--------}
  306. procedure TaaRegexParser.rpParseExpr;
  307. begin
  308.   rpParseTerm;
  309.   if (FPosn^ = '|') then begin
  310.     inc(FPosn);
  311.     writeln('alternation');
  312.     rpParseExpr;
  313.   end;
  314. end;
  315. {--------}
  316. procedure TaaRegexParser.rpParseFactor;
  317. begin
  318.   rpParseAtom;
  319.   case FPosn^ of
  320.     '?' : begin
  321.             inc(FPosn);
  322.             writeln('zero or one');
  323.           end;
  324.     '*' : begin
  325.             inc(FPosn);
  326.             writeln('zero or more');
  327.           end;
  328.     '+' : begin
  329.             inc(FPosn);
  330.             writeln('one or more');
  331.           end;
  332.   end;{case}
  333. end;
  334. {--------}
  335. procedure TaaRegexParser.rpParseTerm;
  336. begin
  337.   rpParseFactor;
  338.   {Note: we have to "break the grammar" here. We've parsed a regular
  339.          subexpression and we're possibly following on with another
  340.          regular subexpression. There's no nice operator to key off
  341.          for concatenation: we just have to know that for
  342.          concatenating two subexpressions, the current character will
  343.          be
  344.            - an open parenthesis
  345.            - an open square bracket
  346.            - an any char operator
  347.            - a character that's not a metacharacter
  348.          i.e., the three possibilities for the start of an "atom" in
  349.          our grammar}
  350.   if (FPosn^ = '(') or
  351.      (FPosn^ = '[') or
  352.      (FPosn^ = '.') or
  353.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then
  354.     rpParseTerm;
  355. end;
  356. {====================================================================}
  357.  
  358.  
  359. {===TaaRegexCompiler===================================================}
  360. constructor TaaRegexCompiler.Create(const aRegexStr : string);
  361. begin
  362.   inherited Create;
  363.   FRegexStr := aRegexStr;
  364.   FIgnoreCase := true;
  365.   FUpcase := System.Upcase;
  366.   FTable := TList.Create;
  367.   FTable.Capacity := 64;
  368. end;
  369. {--------}
  370. destructor TaaRegexCompiler.Destroy;
  371. begin
  372.   if (FTable <> nil) then begin
  373.     rcClear;
  374.     FTable.Free;
  375.   end;
  376.   inherited Destroy;
  377. end;
  378. {--------}
  379. function TaaRegexCompiler.MatchString(const S : string) : integer;
  380. var
  381.   i : integer;
  382.   ErrorPos  : integer;
  383.   ErrorCode : TaaRegexError;
  384. begin
  385.   {if the regex string hasn't been parsed yet, do so}
  386.   if (FTable.Count = 0) then begin
  387.     if not Parse(ErrorPos, ErrorCode) then begin
  388.       raise Exception.Create(
  389.          Format('The regex was invalid at position %d', [ErrorPos]));
  390.     end;
  391.   end;
  392.   {now try and see if the string matches (empty strings don't)}
  393.   Result := 0;
  394.   if (S <> '') then
  395.     {if the regex specified a start anchor, then we only need to check
  396.      the string starting at the first position}
  397.     if FAnchorStart then begin
  398.       if rcMatchSubString(S, 1) then
  399.         Result := 1;
  400.     end
  401.     {otherwise we try and match the string at every position and
  402.      return at the first success}
  403.     else begin
  404.       for i := 1 to length(S) do
  405.         if rcMatchSubString(S, i) then begin
  406.           Result := i;
  407.           Break;
  408.         end;
  409.     end;
  410. end;
  411. {--------}
  412. function TaaRegexCompiler.Parse(var aErrorPos : integer;
  413.                                 var aErrorCode: TaaRegexError)
  414.                                                             : boolean;
  415.   {$IFDEF LogParse}
  416.   procedure WriteError(aErrorPos : integer;
  417.                        aErrorCode: TaaRegexError);
  418.   begin
  419.     writeln(Log, '***parse error found at ', aErrorPos);
  420.     case aErrorCode of
  421.       recNone         : writeln(Log, '-->no error');
  422.       recSuddenEnd    : writeln(Log, '-->unexpected end of regex');
  423.       recMetaChar     : writeln(Log, '-->found metacharacter in wrong place');
  424.       recNoCloseParen : writeln(Log, '-->missing close paren');
  425.       recExtraChars   : writeln(Log, '-->extra chars after valid regex');
  426.     end;
  427.     writeln(Log, '"', FRegexStr, '"');
  428.     writeln(Log, '^':succ(aErrorPos));
  429.   end;
  430.   {$ENDIF}
  431. begin
  432.   {$IFDEF LogParse}
  433.   System.Assign(Log, 'c:\regexparse.log');
  434.   System.Rewrite(Log);
  435.   try
  436.     writeln(Log, 'Parsing regex: "', FRegexStr, '"');
  437.   {$ENDIF}
  438.  
  439.   {clear the current transition table}
  440.   rcClear;
  441.   {empty regex strings are not allowed}
  442.   if (FRegexStr = '') then begin
  443.     Result := false;
  444.     aErrorPos := 1;
  445.     aErrorCode := recSuddenEnd;
  446.  
  447.     {$IFDEF LogParse}
  448.     WriteError(aErrorPos, aErrorCode);
  449.     {$ENDIF}
  450.  
  451.     Exit;
  452.   end;
  453.   {parse the regex string}
  454.   FPosn := PAnsiChar(FRegexStr);
  455.   FStartState := rcParseAnchorExpr;
  456.   {if an error occurred or we're not at the end of the regex string,
  457.    clear the transition table, return false and the error position}
  458.   if (FStartState = ErrorState) or (FPosn^ <> #0) then begin
  459.     if (FStartState <> ErrorState) and (FPosn^ <> #0) then
  460.       FErrorCode := recExtraChars;
  461.     rcClear;
  462.     Result := false;
  463.     aErrorPos := succ(FPosn - PAnsiChar(FRegexStr));
  464.     aErrorCode := FErrorCode;
  465.  
  466.     {$IFDEF LogParse}
  467.     WriteError(aErrorPos, aErrorCode);
  468.     {$ENDIF}
  469.   end
  470.   {otherwise add a terminal state, optimize, return true}
  471.   else begin
  472.     rcAddState(mtTerminal, #0, nil, UnusedState, UnusedState);
  473.     rcLevel1Optimize;
  474.     Result := true;
  475.     aErrorPos := 0;
  476.     aErrorCode := recNone;
  477.  
  478.     {$IFDEF LogParse}
  479.     rcDumpTable;
  480.     {$ENDIF}
  481.   end;
  482.  
  483.   {$IFDEF LogParse}
  484.   finally
  485.     System.Close(Log);
  486.   end;
  487.   {$ENDIF}
  488. end;
  489. {--------}
  490. function TaaRegexCompiler.rcAddState(aMatchType : TaaNFAMatchType;
  491.                                      aChar      : char;
  492.                                      aCharClass : PaaCharSet;
  493.                                      aNextState1: integer;
  494.                                      aNextState2: integer) : integer;
  495. var
  496.   StateData : PaaNFAState;
  497. begin
  498.   {create the new state record}
  499.   New(StateData);
  500.   {set up the fields in the state record}
  501.   if (aNextState1 = NewFinalState) then
  502.     StateData^.sdNextState1 := succ(FTable.Count)
  503.   else
  504.     StateData^.sdNextState1 := aNextState1;
  505.   StateData^.sdNextState2 := aNextState2;
  506.   StateData^.sdMatchType := aMatchType;
  507.   if (aMatchType = mtChar) then
  508.     StateData^.sdChar := aChar
  509.   else if (aMatchType = mtClass) or (aMatchType = mtNegClass) then
  510.     StateData^.sdClass := aCharClass;
  511.   {add the new state}
  512.   Result := FTable.Count;
  513.   FTable.Add(StateData);
  514. end;
  515. {--------}
  516. procedure TaaRegexCompiler.rcClear;
  517. var
  518.   i : integer;
  519.   StateData : PaaNFAState;
  520. begin
  521.   {free all items in the state transition table}
  522.   for i := 0 to pred(FTable.Count) do begin
  523.     StateData := PaaNFAState(FTable.List^[i]);
  524.     if (StateData <> nil) then begin
  525.       with StateData^ do begin
  526.         if (sdMatchType = mtClass) or
  527.            (sdMatchType = mtNegClass) then
  528.           if (sdClass <> nil) then
  529.             FreeMem(StateData^.sdClass);
  530.       end;
  531.       Dispose(StateData);
  532.     end;
  533.   end;
  534.   {clear the state transition table}
  535.   FTable.Clear;
  536.   FTable.Capacity := 64;
  537.   FAnchorStart := false;
  538.   FAnchorEnd := false;
  539. end;
  540. {--------}
  541. {$IFDEF LogParse}
  542. procedure TaaRegexCompiler.rcDumpTable;
  543. var
  544.   i : integer;
  545. begin
  546.   writeln(Log);
  547.   if (FTable.Count = 0) then
  548.     writeln(Log, 'No transition table to dump!')
  549.   else begin
  550.     writeln(Log, 'Transition table dump for "', FRegexStr, '"');
  551.     if FAnchorStart then
  552.       writeln(Log, 'anchored at start of string');
  553.     if FAnchorEnd then
  554.       writeln(Log, 'anchored at end of string');
  555.     writeln(Log, 'start state: ', FStartState:3);
  556.     for i := 0 to pred(FTable.Count) do begin
  557.       write(Log, i:3);
  558.       with PaaNFAState(FTable[i])^ do begin
  559.         case sdMatchType of
  560.           mtNone    : write(Log, '  no match');
  561.           mtAnyChar : write(Log, '  any char');
  562.           mtChar    : write(Log, '    char:', sdChar);
  563.           mtClass   : write(Log, '     class');
  564.           mtNegClass: write(Log, ' neg class');
  565.           mtTerminal: write(Log, '*******END');
  566.           mtUnused  : write(Log, '        --');
  567.         else
  568.           write(Log, ' **error**');
  569.         end;
  570.         if (sdMatchType <> mtTerminal) and
  571.            (sdMatchType <> mtUnused) then
  572.           write(Log, ' next1: ', sdNextState1:3);
  573.         if (sdNextState2 <> UnusedState) then
  574.           write(Log, ' next2: ', sdNextState2:3);
  575.       end;
  576.       writeln(Log);
  577.     end;
  578.   end;
  579. end;
  580. {$ENDIF}
  581. {--------}
  582. procedure TaaRegexCompiler.rcLevel1Optimize;
  583. var
  584.   i : integer;
  585.   Walker : PaaNFAState;
  586. begin
  587.   {level 1 optimization removes all states that have only a single
  588.    no-cost move to another state}
  589.  
  590.   {cycle through all the state records, except for the last one}
  591.   for i := 0 to (FTable.Count - 2) do begin
  592.     {get this state}
  593.     with PaaNFAState(FTable.List^[i])^ do begin
  594.       {walk the chain pointed to by the first next state, unlinking
  595.        the states that are simple single no-cost moves}
  596.       Walker := PaaNFAState(FTable.List^[sdNextState1]);
  597.       while (Walker^.sdMatchType = mtNone) and
  598.             (Walker^.sdNextState2 = UnusedState) do begin
  599.         sdNextState1 := Walker^.sdNextState1;
  600.         Walker := PaaNFAState(FTable.List^[sdNextState1]);
  601.       end;
  602.       {walk the chain pointed to by the first next state, unlinking
  603.        the states that are simple single no-cost moves}
  604.       if (sdNextState2 <> UnusedState) then begin
  605.         Walker := PaaNFAState(FTable.List^[sdNextState2]);
  606.         while (Walker^.sdMatchType = mtNone) and
  607.               (Walker^.sdNextState2 = UnusedState) do begin
  608.           sdNextState2 := Walker^.sdNextState1;
  609.           Walker := PaaNFAState(FTable.List^[sdNextState2]);
  610.         end;
  611.       end;
  612.     end;
  613.   end;
  614.  
  615.   {cycle through all the state records, except for the last one,
  616.    marking unused ones--not strictly necessary but good for debugging}
  617.   for i := 0 to (FTable.Count - 2) do begin
  618.     with PaaNFAState(FTable.List^[i])^ do begin
  619.       if (sdMatchType = mtNone) and
  620.          (sdNextState2 = UnusedState) then
  621.         sdMatchType := mtUnused;
  622.     end;
  623.   end;
  624. end;
  625. {--------}
  626. function TaaRegexCompiler.rcMatchSubString(const S   : string;
  627.                                            StartPosn : integer)
  628.                                                             : boolean;
  629. var
  630.   Ch     : char;
  631.   State  : integer;
  632.   Deque  : TaaIntDeque;
  633.   StrInx : integer;
  634. begin
  635.   {assume we fail to match}
  636.   Result := false;
  637.   {create the deque}
  638.   Deque := TaaIntDeque.Create(64);
  639.   try
  640.     {enqueue the special value to start scanning}
  641.     Deque.Enqueue(MustScan);
  642.     {enqueue the first state}
  643.     Deque.Enqueue(FStartState);
  644.     {prepare the string index}
  645.     StrInx := StartPosn - 1;
  646.     Ch := #0; //just to fool the compiler
  647.     {loop until the deque is empty or we run out of string}
  648.     while (StrInx <= length(S)) and not Deque.IsEmpty do begin
  649.       {pop the top state from the deque}
  650.       State := Deque.Pop;
  651.       {process the "must scan" state first}
  652.       if (State = MustScan) then begin
  653.         {if the deque is empty at this point, we might as well give up
  654.          since there are no states left to process new characters}
  655.         if not Deque.IsEmpty then begin
  656.           {if we haven't run out of string, get the character, and
  657.            enqueue the "must scan" state again}
  658.           inc(StrInx);
  659.           if (StrInx <= length(S)) then begin
  660.             if IgnoreCase then
  661.               Ch := Upcase(S[StrInx])
  662.             else
  663.               Ch := S[StrInx];
  664.             Deque.Enqueue(MustScan);
  665.           end;
  666.         end;
  667.       end
  668.       {otherwise, process the state}
  669.       else with PaaNFAState(FTable.List^[State])^ do begin
  670.         case sdMatchType of
  671.           mtNone :
  672.             begin
  673.               {for free moves, push the next states onto the deque}
  674.               Assert(sdNextState2 <> UnusedState,
  675.                      'optimization should remove all states with one no-cost move');
  676.               Deque.Push(sdNextState2);
  677.               Deque.Push(sdNextState1);
  678.             end;
  679.           mtAnyChar :
  680.             begin
  681.               {for a match of any character, enqueue the next state}
  682.               Deque.Enqueue(sdNextState1);
  683.             end;
  684.           mtChar :
  685.             begin
  686.               {for a match of a character, enqueue the next state}
  687.               if (Ch = sdChar) then
  688.                 Deque.Enqueue(sdNextState1);
  689.             end;
  690.           mtClass :
  691.             begin
  692.               {for a match within a class, enqueue the next state}
  693.               if (Ch in sdClass^) then
  694.                 Deque.Enqueue(sdNextState1);
  695.             end;
  696.           mtNegClass :
  697.             begin
  698.               {for a match not within a class, enqueue the next state}
  699.               if not (Ch in sdClass^) then
  700.                 Deque.Enqueue(sdNextState1);
  701.             end;
  702.           mtTerminal :
  703.             begin
  704.               {for a terminal state, the string successfully matched
  705.                if the regex had no end anchor, or we're at the end
  706.                of the string}
  707.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  708.                 Result := true;
  709.                 Exit;
  710.               end;
  711.             end;
  712.           mtUnused :
  713.             begin
  714.               Assert(false, 'unused states shouldn''t be seen');
  715.             end;
  716.         end;
  717.       end;
  718.     end;
  719.     {if we reach this point we've either exhausted the deque or we've
  720.      run out of string; if the former, the substring did not match
  721.      since there are no more states. If the latter, we need to check
  722.      the states left on the deque to see if one is the terminating
  723.      state; if so the string matched the regular expression defined by
  724.      the transition table}
  725.     while not Deque.IsEmpty do begin
  726.       State := Deque.Pop;
  727.       with PaaNFAState(FTable.List^[State])^ do begin
  728.         case sdMatchType of
  729.           mtNone :
  730.             begin
  731.               {for free moves, push the next states onto the deque}
  732.               Assert(sdNextState2 <> UnusedState,
  733.                      'optimization should remove all states with one no-cost move');
  734.               Deque.Push(sdNextState2);
  735.               Deque.Push(sdNextState1);
  736.             end;
  737.           mtTerminal :
  738.             begin
  739.               {for a terminal state, the string successfully matched
  740.                if the regex had no end anchor, or we're at the end
  741.                of the string}
  742.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  743.                 Result := true;
  744.                 Exit;
  745.               end;
  746.             end;
  747.         end;{case}
  748.       end;
  749.     end;
  750.   finally
  751.     Deque.Free;
  752.   end;
  753. end;
  754. {--------}
  755. function TaaRegexCompiler.rcParseAnchorExpr : integer;
  756. begin
  757.   {check for an initial '^'}
  758.   if (FPosn^ = '^') then begin
  759.     FAnchorStart := true;
  760.     inc(FPosn);
  761.  
  762.     {$IFDEF LogParse}
  763.     writeln(Log, 'parsed start anchor');
  764.     {$ENDIF}
  765.   end;
  766.  
  767.   {parse an expression}
  768.   Result := rcParseExpr;
  769.  
  770.   {if we were successful, check for the final '$'}
  771.   if (Result <> ErrorState) then begin
  772.     if (FPosn^ = '$') then begin
  773.       FAnchorEnd := true;
  774.       inc(FPosn);
  775.  
  776.       {$IFDEF LogParse}
  777.       writeln(Log, 'parsed end anchor');
  778.       {$ENDIF}
  779.     end;
  780.   end;
  781. end;
  782. {--------}
  783. function TaaRegexCompiler.rcParseAtom : integer;
  784. var
  785.   MatchType : TaaNFAMatchType;
  786.   CharClass : PaaCharSet;
  787. begin
  788.   case FPosn^ of
  789.     '(' :
  790.       begin
  791.         {move past the open parenthesis}
  792.         inc(FPosn);
  793.  
  794.         {$IFDEF LogParse}
  795.         writeln(Log, 'parsed open paren');
  796.         {$ENDIF}
  797.  
  798.         {parse a complete regex between the parentheses}
  799.         Result := rcParseExpr;
  800.         if (Result = ErrorState) then
  801.           Exit;
  802.         {if the current character is not a close parenthesis,
  803.          there's an error}
  804.         if (FPosn^ <> ')') then begin
  805.           FErrorCode := recNoCloseParen;
  806.           Result := ErrorState;
  807.           Exit;
  808.         end;
  809.         {move past the close parenthesis}
  810.         inc(FPosn);
  811.  
  812.         {$IFDEF LogParse}
  813.         writeln(Log, 'parsed close paren');
  814.         {$ENDIF}
  815.       end;
  816.     '[' :
  817.       begin
  818.         {move past the open square bracket}
  819.         inc(FPosn);
  820.  
  821.         {$IFDEF LogParse}
  822.         writeln(Log, 'parsed open square bracket (start of class)');
  823.         {$ENDIF}
  824.  
  825.         {if the first character in the class is a '^' then the
  826.          class if negated, otherwise it's a normal one}
  827.         if (FPosn^ = '^') then begin
  828.           inc(FPosn);
  829.           MatchType := mtNegClass;
  830.  
  831.           {$IFDEF LogParse}
  832.           writeln(Log, 'it is a negated class');
  833.           {$ENDIF}
  834.         end
  835.         else begin
  836.           MatchType := mtClass;
  837.  
  838.           {$IFDEF LogParse}
  839.           writeln(Log, 'it is a normal class');
  840.           {$ENDIF}
  841.         end;
  842.         {allocate the class character set and parse the character
  843.          class; this will return either with an error, or when the
  844.          closing square bracket is encountered}
  845.         New(CharClass);
  846.         CharClass^ := [];
  847.         if not rcParseCharClass(CharClass) then begin
  848.           Dispose(CharClass);
  849.           Result := ErrorState;
  850.           Exit;
  851.         end;
  852.         {move past the closing square bracket}
  853.         Assert(FPosn^ = ']',
  854.                'the rcParseCharClass terminated without finding a "]"');
  855.         inc(FPosn);
  856.  
  857.         {$IFDEF LogParse}
  858.         writeln(Log, 'parsed close square bracket (end of class)');
  859.         {$ENDIF}
  860.  
  861.         {add a new state for the character class}
  862.         Result := rcAddState(MatchType, #0, CharClass,
  863.                              NewFinalState, UnusedState);
  864.       end;
  865.     '.' :
  866.       begin
  867.         {move past the period metacharacter}
  868.         inc(FPosn);
  869.  
  870.         {$IFDEF LogParse}
  871.         writeln(Log, 'parsed anychar operator "."');
  872.         {$ENDIF}
  873.  
  874.         {add a new state for the 'any character' token}
  875.         Result := rcAddState(mtAnyChar, #0, nil,
  876.                              NewFinalState, UnusedState);
  877.       end;
  878.   else
  879.     {otherwise parse a single character}
  880.     Result := rcParseChar;
  881.   end;{case}
  882. end;
  883. {--------}
  884. function TaaRegexCompiler.rcParseCCChar : char;
  885. begin
  886.   {if we hit the end of the string, it's an error}
  887.   if (FPosn^ = #0) then begin
  888.     FErrorCode := recSuddenEnd;
  889.     Result := #0;
  890.     Exit;
  891.   end;
  892.   {if the current char is a metacharacter (at least in terms of a
  893.    character class), it's an error}
  894.   if FPosn^ in [']', '-'] then begin
  895.     FErrorCode := recMetaChar;
  896.     Result := #0;
  897.     Exit;
  898.   end;
  899.   {otherwise return the character and advance past it}
  900.   if (FPosn^ = '\') then
  901.     {..it's an escaped character: get the next character instead}
  902.     inc(FPosn);
  903.   Result := FPosn^;
  904.   inc(FPosn);
  905.  
  906.   {$IFDEF LogParse}
  907.   writeln(Log, 'parsed charclass char: "', Result, '"');
  908.   {$ENDIF}
  909. end;
  910. {--------}
  911. function TaaRegexCompiler.rcParseChar : integer;
  912. var
  913.   Ch : char;
  914. begin
  915.   {if we hit the end of the string, it's an error}
  916.   if (FPosn^ = #0) then begin
  917.     Result := ErrorState;
  918.     FErrorCode := recSuddenEnd;
  919.     Exit;
  920.   end;
  921.   {if the current char is one of the metacharacters, it's an error}
  922.   if FPosn^ in MetaCharacters then begin
  923.     Result := ErrorState;
  924.     FErrorCode := recMetaChar;
  925.     Exit;
  926.   end;
  927.   {otherwise add a state for the character}
  928.   {..if it's an escaped character: get the next character instead}
  929.   if (FPosn^ = '\') then
  930.     inc(FPosn);
  931.   if IgnoreCase then
  932.     Ch := Upcase(FPosn^)
  933.   else
  934.     Ch := FPosn^;
  935.   Result := rcAddState(mtChar, Ch, nil, NewFinalState, UnusedState);
  936.   inc(FPosn);
  937.  
  938.   {$IFDEF LogParse}
  939.   writeln(Log, 'parsed char: "', Ch, '"');
  940.   {$ENDIF}
  941. end;
  942. {--------}
  943. function TaaRegexCompiler.rcParseCharClass(aClass : PaaCharSet) : boolean;
  944. begin
  945.   {assume we can't parse a character class properly}
  946.   Result := false;
  947.   {parse a character range; if we can't there was an error and the
  948.    caller will take care of it}
  949.   if not rcParseCharRange(aClass) then
  950.     Exit;
  951.   {if the current character was not the right bracket, parse another
  952.    character class (note: we're removing the tail recursion here)}
  953.   while (FPosn^ <> ']') do begin
  954.     if not rcParseCharRange(aClass) then
  955.       Exit;
  956.   end;
  957.   {if we reach here we were successful}
  958.   Result := true;
  959. end;
  960. {--------}
  961. function TaaRegexCompiler.rcParseCharRange(aClass : PaaCharSet) : boolean;
  962. var
  963.   StartChar : char;
  964.   EndChar   : char;
  965.   Ch        : char;
  966. begin
  967.   {assume we can't parse a character range properly}
  968.   Result := false;
  969.   {parse a single character; if it's null there was an error}
  970.   StartChar := rcParseCCChar;
  971.   if (StartChar = #0) then
  972.     Exit;
  973.   {if the current character is not a dash, the range consisted of a
  974.    single character}
  975.   if (FPosn^ <> '-') then begin
  976.     if IgnoreCase then
  977.       Include(aClass^, Upcase(StartChar))
  978.     else
  979.       Include(aClass^, StartChar)
  980.   end
  981.   {otherwise it's a real range, so get the character at the end of the
  982.    range; if that's null, there was an error}
  983.   else begin
  984.  
  985.     {$IFDEF LogParse}
  986.     writeln(Log, '-range to-');
  987.     {$ENDIF}
  988.  
  989.     inc(FPosn); {move past the '-'}
  990.     EndChar := rcParseCCChar;
  991.     if (EndChar = #0) then
  992.       Exit;
  993.     {build the range as a character set}
  994.     if (StartChar > EndChar) then begin
  995.       Ch := StartChar;
  996.       StartChar := EndChar;
  997.       EndChar := Ch;
  998.     end;
  999.     for Ch := StartChar to EndChar do begin
  1000.       Include(aClass^, Ch);
  1001.       if IgnoreCase then
  1002.         Include(aClass^, Upcase(Ch));
  1003.     end;
  1004.   end;
  1005.   {if we reach here we were successful}
  1006.   Result := true;
  1007. end;
  1008. {--------}
  1009. function TaaRegexCompiler.rcParseExpr : integer;
  1010. var
  1011.   StartState1 : integer;
  1012.   StartState2 : integer;
  1013.   EndState1   : integer;
  1014.   OverallStartState : integer;
  1015. begin
  1016.   {assume the worst}
  1017.   Result := ErrorState;
  1018.   {parse an initial term}
  1019.   StartState1 := rcParseTerm;
  1020.   if (StartState1 = ErrorState) then
  1021.     Exit;
  1022.   {if the current character is *not* a pipe character, no alternation
  1023.    is present so return the start state of the initial term as our
  1024.    start state}
  1025.   if (FPosn^ <> '|') then
  1026.     Result := StartState1
  1027.   {otherwise, we need to parse another expr and join the two together
  1028.    in the transition table}
  1029.   else begin
  1030.  
  1031.     {$IFDEF LogParse}
  1032.     writeln(Log, 'OR (alternation)');
  1033.     {$ENDIF}
  1034.  
  1035.     {advance past the pipe}
  1036.     inc(FPosn);
  1037.     {the initial term's end state does not exist yet (although there
  1038.      is a state in the term that points to it), so create it}
  1039.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1040.     {for the OR construction we need a new initial state: it will
  1041.      point to the initial term and the second just-about-to-be-parsed
  1042.      expr}
  1043.     OverallStartState := rcAddState(mtNone, #0, nil,
  1044.                                     UnusedState, UnusedState);
  1045.     {parse another expr}
  1046.     StartState2 := rcParseExpr;
  1047.     if (StartState2 = ErrorState) then
  1048.       Exit;
  1049.     {alter the state state for the overall expr so that the second
  1050.      link points to the start of the second expr}
  1051.     Result := rcSetState(OverallStartState, StartState1, StartState2);
  1052.     {now set the end state for the initial term to point to the final
  1053.      end state for the second expr and the overall expr}
  1054.     rcSetState(EndState1, FTable.Count, UnusedState);
  1055.   end;
  1056. end;
  1057. {--------}
  1058. function TaaRegexCompiler.rcParseFactor : integer;
  1059. var
  1060.   StartStateAtom : integer;
  1061.   EndStateAtom   : integer;
  1062. begin
  1063.   {assume the worst}
  1064.   Result := ErrorState;
  1065.   {first parse an atom}
  1066.   StartStateAtom := rcParseAtom;
  1067.   if (StartStateAtom = ErrorState) then
  1068.     Exit;
  1069.   {check for a closure operator}
  1070.   case FPosn^ of
  1071.     '?' : begin
  1072.             {$IFDEF LogParse}
  1073.             writeln(Log, 'zero or one closure');
  1074.             {$ENDIF}
  1075.  
  1076.             {move past the ? operator}
  1077.             inc(FPosn);
  1078.             {the atom's end state doesn't exist yet, so create one}
  1079.             EndStateAtom := rcAddState(mtNone, #0, nil,
  1080.                                        UnusedState, UnusedState);
  1081.             {create a new start state for the overall regex}
  1082.             Result := rcAddState(mtNone, #0, nil,
  1083.                                  StartStateAtom, EndStateAtom);
  1084.             {make sure the new end state points to the next unused
  1085.              state}
  1086.             rcSetState(EndStateAtom, FTable.Count, UnusedState);
  1087.           end;
  1088.     '*' : begin
  1089.             {$IFDEF LogParse}
  1090.             writeln(Log, 'zero or more closure');
  1091.             {$ENDIF}
  1092.  
  1093.             {move past the * operator}
  1094.             inc(FPosn);
  1095.             {the atom's end state doesn't exist yet, so create one;
  1096.              it'll be the start of the overall regex subexpression}
  1097.             Result := rcAddState(mtNone, #0, nil,
  1098.                                  NewFinalState, StartStateAtom);
  1099.           end;
  1100.     '+' : begin
  1101.             {$IFDEF LogParse}
  1102.             writeln(Log, 'one or more closure');
  1103.             {$ENDIF}
  1104.  
  1105.             {move past the + operator}
  1106.             inc(FPosn);
  1107.             {the atom's end state doesn't exist yet, so create one}
  1108.             rcAddState(mtNone, #0, nil, NewFinalState, StartStateAtom);
  1109.             {the start of the overall regex subexpression will be the
  1110.              atom's start state}
  1111.             Result := StartStateAtom;
  1112.           end;
  1113.   else
  1114.     Result := StartStateAtom;
  1115.   end;{case}
  1116. end;
  1117. {--------}
  1118. function TaaRegexCompiler.rcParseTerm : integer;
  1119. var
  1120.   StartState2 : integer;
  1121.   EndState1   : integer;
  1122. begin
  1123.   {parse an initial factor, the state number returned will also be our
  1124.    return state number}
  1125.   Result := rcParseFactor;
  1126.   if (Result = ErrorState) then
  1127.     Exit;
  1128.   {Note: we have to "break the grammar" here. We've parsed a regular
  1129.          subexpression and we're possibly following on with another
  1130.          regular subexpression. There's no nice operator to key off
  1131.          for concatenation: we just have to know that for
  1132.          concatenating two subexpressions, the current character will
  1133.          be
  1134.            - an open parenthesis
  1135.            - an open square bracket
  1136.            - an any char operator
  1137.            - a character that's not a metacharacter
  1138.          i.e., the three possibilities for the start of an "atom" in
  1139.          our grammar}
  1140.   if (FPosn^ = '(') or
  1141.      (FPosn^ = '[') or
  1142.      (FPosn^ = '.') or
  1143.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then begin
  1144.     {$IFDEF LogParse}
  1145.     writeln(Log, 'concatenation');
  1146.     {$ENDIF}
  1147.  
  1148.     {the initial factor's end state does not exist yet (although there
  1149.      is a state in the term that points to it), so create it}
  1150.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1151.     {parse another term}
  1152.     StartState2 := rcParseTerm;
  1153.     if (StartState2 = ErrorState) then begin
  1154.       Result := ErrorState;
  1155.       Exit;
  1156.     end;
  1157.     {join the first factor to the second term}
  1158.     rcSetState(EndState1, StartState2, UnusedState);
  1159.   end;
  1160. end;
  1161. {--------}
  1162. procedure TaaRegexCompiler.rcSetIgnoreCase(aValue : boolean);
  1163. begin
  1164.   if (aValue <> FIgnoreCase) then begin
  1165.     rcClear;
  1166.     FIgnoreCase := aValue;
  1167.   end;
  1168. end;
  1169. {--------}
  1170. procedure TaaRegexCompiler.rcSetRegexStr(const aRegexStr : string);
  1171. begin
  1172.   if (aRegexStr <> FRegexStr) then begin
  1173.     rcClear;
  1174.     FRegexStr := aRegexStr;
  1175.   end;
  1176. end;
  1177. {--------}
  1178. function TaaRegexCompiler.rcSetState(aState     : integer;
  1179.                                      aNextState1: integer;
  1180.                                      aNextState2: integer) : integer;
  1181. var
  1182.   StateData : PaaNFAState;
  1183. begin
  1184.   Assert((0 <= aState) and (aState < FTable.Count),
  1185.          'trying to change an invalid state');
  1186.  
  1187.   {get the state record and change the transition information}
  1188.   StateData := PaaNFAState(FTable.List^[aState]);
  1189.   StateData^.sdNextState1 := aNextState1;
  1190.   StateData^.sdNextState2 := aNextState2;
  1191.   Result := aState;
  1192. end;
  1193. {--------}
  1194. procedure TaaRegexCompiler.rcSetUpcase(aValue : TaaUpcaseChar);
  1195. begin
  1196.   if not Assigned(aValue) then
  1197.     FUpcase := System.Upcase
  1198.   else
  1199.     FUpcase := aValue;
  1200. end;
  1201. {====================================================================}
  1202.  
  1203. end.
  1204.